import {
    MsgHeader,
    MsgType,
    SmsReq,
    UserStatus,
    UserInfoResp,
    FileDownloadNotice,
    FileDownloadReq,
    FileDownloadRes,
    FileUploadReq,
    FileUploadResp,
    HeartBeat,
    LoginReq,
    LoginResp,
    ConnectReq,
    ConnectResp
} from "./security_msg_pb";

const fs = require("fs");
const crypto = require("crypto");
const { dialog } = require("electron").remote;
const { Writable } = require("stream");
const path = require("path");
const net = require("net");

/**
 * 连接事件处理
 * 
 * @param {*} socket 
 * @param {*} userID 
 */
function ConnectEvent(socket, userID) {
    return new Promise((resolve, reject) => {
        fs.open("./data/.shadow/server.pem", "r", (err, fd) => {
            let connectReq = new ConnectReq();
            connectReq.setTimestamp(new Date().getTime());
            if (err) {
                connectReq.setIsNeedKey(true);
            } else {
                connectReq.setIsNeedKey(false);
            }
            let connectReqBuffer = connectReq.serializeBinary();
            let msgHeader = new MsgHeader();
            msgHeader.setMsgSize(connectReqBuffer.length);
            msgHeader.setMsgId(0);
            msgHeader.setMsgType(MsgType.CONNECT_REQ);
            msgHeader.setFrom(userID);
            msgHeader.setTo(0); // 发送给服务器
            const headerBuffer = msgHeader.serializeBinary();
            let packageHeader = Buffer.alloc(8);
            packageHeader.write("AB47");
            packageHeader.writeInt32LE(headerBuffer.length, 4);
            const packageBuffer = Buffer.concat([
                packageHeader,
                headerBuffer,
                connectReqBuffer
            ]);
            socket.write(packageBuffer, () => {
                socket.once("data", data => {
                    let flag = data.toString("utf8", 0, 4);
                    if (flag !== "AB47") {
                        return;
                    }
                    let headerSize = data.readInt32LE(4);
                    // 消息头反向序列化
                    let msgHeader = MsgHeader.deserializeBinary(
                        data.subarray(8, headerSize + 8)
                    );
                    // 消息类型
                    let msgType = msgHeader.getMsgType();
                    // 消息大小
                    let msgSize = msgHeader.getMsgSize();
                    if (msgType !== MsgType.CONNECT_RESP) {
                        reject("ConnectEvent RES MsgType Error!");
                    } else {
                        let resp = ConnectResp.deserializeBinary(
                            data.subarray(8 + headerSize, msgSize + 8 + headerSize)
                        );
                        if (resp.getPubKey() !== "") {
                            fs.writeFile(
                                "./data/.shadow/server.pem",
                                resp.getPubKey(),
                                err => {
                                    if (err) {
                                        reject(err);
                                    };
                                    // 连接完成后进行登录
                                    resolve(resp);
                                }
                            );
                        } else {
                            resolve(resp);
                        }
                    }
                })
            });
        });
    });
}

/**
 * 登录事件处理流程
 * 
 * @param {*} socket 
 * @param {*} userID 
 * @param {*} username 
 * @param {*} password 
 */
function LoginEvent(socket, userID, username, password) {
    return new Promise((resolve, reject) => {
        let loginReq = new LoginReq();
        loginReq.setUsername(username);
        loginReq.setPassword(password);
        fs.open("./data/.shadow/mine.pem", "r", (err, fd) => {
            if (err) {
                loginReq.setIsNeedKey(true);
            } else {
                loginReq.setIsNeedKey(false);
            }

            // 读取服务器通讯公钥
            fs.readFile("./data/.shadow/server.pem", (err, data) => {
                if (err) {
                    reject(err);
                }
                let cipherBuffer = crypto.publicEncrypt(
                    {
                        key: data.toString(), // 秘钥
                        padding: crypto.constants.RSA_PKCS1_PADDING // 填充方式
                    },
                    loginReq.serializeBinary()
                );
                // 构造消息头
                let msgHeader = new MsgHeader();
                msgHeader.setMsgSize(cipherBuffer.length);
                msgHeader.setMsgId(0);
                msgHeader.setMsgType(MsgType.CLIENT_LOGIN_REQ);
                msgHeader.setFrom(userID);
                msgHeader.setTo(0); // 发送给服务器
                const headerBuffer = msgHeader.serializeBinary();
                let packageHeader = Buffer.alloc(8);
                packageHeader.write("AB47");
                packageHeader.writeInt32LE(headerBuffer.length, 4);
                const packageBuffer = Buffer.concat([
                    packageHeader,
                    headerBuffer,
                    cipherBuffer
                ]);
                socket.write(packageBuffer, () => {
                    socket.once("data", data => {
                        let flag = data.toString("utf8", 0, 4);
                        if (flag !== "AB47") {
                            return;
                        }
                        let headerSize = data.readInt32LE(4);
                        // 消息头反向序列化
                        let msgHeader = MsgHeader.deserializeBinary(
                            data.subarray(8, headerSize + 8)
                        );
                        // 消息类型
                        let msgType = msgHeader.getMsgType();
                        // 消息大小
                        let msgSize = msgHeader.getMsgSize();
                        if (msgType !== MsgType.CLIENT_LOGIN_RESP) {
                            reject("LoginEvent RES MsgType Error!");
                        } else {
                            let res = LoginResp.deserializeBinary(
                                data.subarray(8 + headerSize, msgSize + 8 + headerSize)
                            )
                            resolve(res);
                        }
                    });
                });
            });
        });
    });
}

/**
 * 用户在线状态流程
 * 
 * @param {*} socket 
 * @param {*} userID 
 * @param {*} userToken 
 */
function UserStatusEvent(socket, userID, userToken) {
    return new Promise((resolve, reject) => {
        let msgHeader = new MsgHeader();
        msgHeader.setFrom(userID);
        msgHeader.setTo(0);
        msgHeader.setMsgSize(0);
        msgHeader.setMsgType(MsgType.USER_STATUS_REQ);
        msgHeader.setToken(userToken);
        const headerBuffer = msgHeader.serializeBinary();
        let packageHeader = Buffer.alloc(8);
        packageHeader.write("AB47");
        packageHeader.writeInt32LE(headerBuffer.length, 4);
        const packageBuffer = Buffer.concat([packageHeader, headerBuffer]);
        socket.write(packageBuffer, () => {
            socket.once("data", data => {
                let flag = data.toString("utf8", 0, 4);
                if (flag !== "AB47") {
                    return;
                }
                let headerSize = data.readInt32LE(4);
                // 消息头反向序列化
                let msgHeader = MsgHeader.deserializeBinary(
                    data.subarray(8, headerSize + 8)
                );
                // 消息类型
                let msgType = msgHeader.getMsgType();
                // 消息大小
                let msgSize = msgHeader.getMsgSize();
                if (msgType !== MsgType.USER_STATUS_RESP) {
                    reject("UserStatusEvent RES MsgType Error!");
                } else {
                    let res = UserStatus.deserializeBinary(
                        data.subarray(8 + headerSize, msgSize + 8 + headerSize)
                    );
                    resolve(res);
                }
            });
        });
    });
}

/**
 * 文件上传流程
 * 
 * @param {*} selfId 
 * @param {*} userToken 
 * @param {*} otherId 
 */
function UploadEvent(selfId, userToken, otherId) {
    dialog.showOpenDialog(require("electron").remote.getCurrentWindow(), {
        title: "选择上传文件",
        properties: ["openFile"]
    }).then(result => {
        return new Promise((resolve, reject) => {
            if (result.canceled) {
                reject(result);
            }
            resolve(result.filePaths[0]);
        });
    }).then(filePath => { // 上传文件的本地路径
        return new Promise((resolve, reject) => {
            const hash = crypto.createHash("MD5");
            const input = fs.createReadStream(filePath);
            input.pipe(hash).pipe(
                new Writable({
                    write(chunk, encoding, callback) {
                        resolve({ md5: chunk, path: filePath });
                    }
                })
            );
        })
    }).then(obj => { // 文件属性
        return new Promise((resolve, reject) => {
            fs.stat(obj.path, (err, stats) => {
                if (err) {
                    reject(err);
                }
                obj['stats'] = stats
                resolve(obj);
            });
        });
    }).then(obj => { // 用户私钥
        return new Promise((resolve, reject) => {
            fs.readFile("./data/.shadow/mine.pem", (err, mine) => {
                if (err) {
                    reject(err);
                }
                obj['mine'] = mine;
                resolve(obj);
            });
        });
    }).then(obj => {
        const privateKey = crypto.createPrivateKey(obj.mine);
        const sign = crypto.createSign("SHA256");
        sign.update(obj.md5.toString("hex"));
        sign.end();
        const signature = sign.sign(privateKey);
        obj['signature'] = signature.toString("hex")
        return new Promise((resolve, reject) => {
            fs.readFile("./data/bootstrap.json", (err, data) => {
                if (err) {
                    reject(err);
                }
                const bootstrap = JSON.parse(data.toString());
                obj['host'] = bootstrap.socket.host;
                obj['port'] = bootstrap.stream.port;
                resolve(obj);
            });
        });
    }).then(obj => { // 执行上传获取应答
        let fileUploadReq = new FileUploadReq();
        fileUploadReq.setFileName(path.basename(obj.path));
        fileUploadReq.setFileSize(obj.stats.size);
        fileUploadReq.setSignature(obj.signature);
        let fileUploadReqBuff = fileUploadReq.serializeBinary();
        let msgHeader = new MsgHeader();
        msgHeader.setMsgSize(fileUploadReqBuff.length);
        msgHeader.setMsgType(MsgType.FILE_UPLOAD_REQ);
        msgHeader.setFrom(selfId);
        msgHeader.setTo(otherId);
        msgHeader.setToken(userToken);
        const headerBuf = msgHeader.serializeBinary();
        let pkgHeader = Buffer.alloc(8);
        pkgHeader.write("AB47");
        pkgHeader.writeInt32LE(headerBuf.length, 4);
        let requestBuffer = Buffer.concat([
            pkgHeader,
            headerBuf,
            fileUploadReqBuff
        ]); // 拼装请求数据buf
        return new Promise((resolve, reject) => {
            const client = net.createConnection(obj.port, obj.host, () => {
                client.write(requestBuffer, () => {
                    client.once("data", data => {
                        let flag = data.toString("utf8", 0, 4);
                        if (flag !== "AB47") {
                            return;
                        }
                        let headerSize = data.readInt32LE(4);
                        // 消息头反向序列化
                        let msgHeader = MsgHeader.deserializeBinary(
                            data.subarray(8, headerSize + 8)
                        );
                        // 消息类型
                        let msgType = msgHeader.getMsgType();
                        // 消息大小
                        let msgSize = msgHeader.getMsgSize();

                        if (msgType !== MsgType.FILE_UPLOAD_RESP) {
                            reject("UploadEvent RES MsgType Error!");
                        } else {
                            let fileUploadResp = FileUploadResp.deserializeBinary(
                                data.subarray(8 + headerSize, msgSize + 8 + headerSize)
                            );
                            if (fileUploadResp.getRespType() !== FileUploadResp.FileUploadRespType.OK) {
                                reject("不允许上传");
                            };
                            obj['netSocket'] = client;
                            resolve(obj);
                        }
                    });
                });
            });
        });
    }).then(obj => {
        const rs = fs.createReadStream(obj.path);
        rs.pipe(obj.netSocket);
        rs.on("end", () => {
            obj.netSocket.once("end", () => {
                console.log("上传连接关闭");
            })
        });
    });
}

/**
 * 文件下载流程
 * 
 * @param {*} selfId 
 * @param {*} selfToken 
 * @param {*} header 
 * @param {*} fileDownloadNotice 
 */
function DownloadEvent(selfId, selfToken, header, fileDownloadNotice) {
    new Promise((resolve, reject) => {
        fs.readFile("./data/bootstrap.json", (err, data) => {
            if (err) {
                reject(err);
            }
            const bootstrap = JSON.parse(data.toString());
            resolve({ host: bootstrap.socket.host, port: bootstrap.stream.port });
        });
    }).then(obj => { // 下载
        let fileDownloadReq = new FileDownloadReq();
        fileDownloadReq.setFileName(fileDownloadNotice.getFileName());
        const fileDownloadReqBuf = fileDownloadReq.serializeBinary();
        let msgHeader = new MsgHeader();
        msgHeader.setMsgSize(fileDownloadReqBuf.length);
        msgHeader.setMsgType(MsgType.FILE_DOWNLOAD_REQ); // 下载请求类型
        msgHeader.setFrom(selfId);
        msgHeader.setTo(0);
        msgHeader.setToken(selfToken);
        const headerBuf = msgHeader.serializeBinary();
        let pkgHeader = Buffer.alloc(8);
        pkgHeader.write("AB47");
        pkgHeader.writeInt32LE(headerBuf.length, 4);
        let requestBuffer = Buffer.concat([
            pkgHeader,
            headerBuf,
            fileDownloadReqBuf
        ]); // 拼装请求数据buf
        return new Promise((resolve, reject) => {
            const client = net.createConnection(obj.port, obj.host, () => {
                client.write(requestBuffer, () => {
                    const ws = fs.createWriteStream(
                        "storage/" + fileDownloadNotice.getFileName()
                    );
                    let recvSize = 0;
                    client.once("data", data => {
                        recvSize += data.length;
                        if (recvSize === fileDownloadNotice.getFileSize()) {
                            resolve(client)
                        }
                    });
                    client.pipe(ws);
                });
            });
        });
    }).then((netSocket) => { // 生成签名
        const input = fs.createReadStream(
            "storage/" + fileDownloadNotice.getFileName()
        );
        const hash = crypto.createHash("MD5");
        return new Promise((resolve, reject) => {
            input.pipe(hash).pipe(new Writable({
                write(chunk, encoding, callback) {
                    const md5 = chunk.toString("hex");
                    resolve({ md5: md5, netSocket: netSocket });
                }
            }));
        });
    }).then(obj => { // 签名验证
        return new Promise((resolve, reject) => {
            fs.readFile("./data/.shadow/pub/" + header.getFrom() + ".pem", (err, pem) => {
                if (err) {
                    reject(err);
                }
                const publicKey = crypto.createPublicKey(pem);
                const verify = crypto.createVerify("SHA256");
                verify.update(obj.md5);
                verify.end();
                const verifyRes = verify.verify(publicKey, Buffer.from(fileDownloadNotice.getSignature(), "hex"));
                if (!verifyRes) {
                    reject("下载文件签名验证错误");
                } else {
                    resolve(obj);
                }
            });
        });
    }).then(obj => { // 发送应答
        let fileDownloadRes = new FileDownloadRes();
        fileDownloadRes.setFileName(fileDownloadNotice.getFileName());
        fileDownloadRes.setResType(FileDownloadRes.FileDownloadResType.OK);
        let msgHeader = new MsgHeader();
        let fileDownloadResBuf = fileDownloadRes.serializeBinary();
        msgHeader.setMsgSize(fileDownloadResBuf.length);
        msgHeader.setMsgType(MsgType.FILE_DOWNLOAD_RES); // 下载请求类型
        msgHeader.setFrom(selfId);
        msgHeader.setTo(0);
        msgHeader.setToken(selfToken);
        const headerBuf = msgHeader.serializeBinary();
        let pkgHeader = Buffer.alloc(8);
        pkgHeader.write("AB47");
        pkgHeader.writeInt32LE(headerBuf.length, 4);
        let requestBuffer = Buffer.concat([
            pkgHeader,
            headerBuf,
            fileDownloadResBuf
        ]);
        obj.netSocket.write(requestBuffer, () => {
            obj.netSocket.end();
            console.log("下载完成");
        });
    });
}

export {
    ConnectEvent, LoginEvent, UserStatusEvent, UploadEvent, DownloadEvent
}
